﻿using EasyNetQ;
using EasyNetQ.Topology;
using System.Collections.Concurrent;

namespace Magic.Rmq;

public interface IRmqFactory
{
    IBus Create(string connectionStringOrName);
    IExchange GetExchange(string connectionStringOrName);
    IPullingConsumer<PullResult> GetPullingConsumer(string connectionStringOrName, string queueName);
}

public class RmqFactory : IRmqFactory
{
    private readonly ConcurrentDictionary<string, Lazy<IBus>> _clients;
    private readonly ConcurrentDictionary<string, IExchange> _exchanges;
    private readonly ConcurrentDictionary<string, IPullingConsumer<PullResult>> _pulling;
    private readonly Func<string, Lazy<IBus>> _entryFactory;
    private readonly Func<string, IExchange> _exchangeFactory;
    private readonly Func<string, string, IPullingConsumer<PullResult>> _pullingFactory;

    public RmqFactory()
    {
        _clients = new ConcurrentDictionary<string, Lazy<IBus>>();
        _exchanges = new ConcurrentDictionary<string, IExchange>();
        _pulling = new ConcurrentDictionary<string, IPullingConsumer<PullResult>>();
        _entryFactory = (name) =>
        {
            return new Lazy<IBus>(() => CreateClient(), LazyThreadSafetyMode.ExecutionAndPublication);
        };
        _exchangeFactory = (name) =>
        {
            var bus = Create(name);
            return bus.Advanced.ExchangeDeclare("Gate", ExchangeType.Topic);
        };
        _pullingFactory = (connectionStringOrName, queueName) =>
        {
            var bus = Create(connectionStringOrName);
            return bus.Advanced.CreatePullingConsumer(new Queue(queueName));
        };
    }
    IBus CreateClient()
    {
        return RabbitHutch.CreateBus(RmqOptions.ConnectionString);
    }
    public IBus Create(string connectionStringOrName)
    {
        return _clients.GetOrAdd(connectionStringOrName, _entryFactory).Value;
    }

    public IExchange GetExchange(string connectionStringOrName)
    {
        return _exchanges.GetOrAdd(connectionStringOrName, _exchangeFactory);
    }

    public IPullingConsumer<PullResult> GetPullingConsumer(string connectionStringOrName, string queueName)
    {
        return _pulling.GetOrAdd(connectionStringOrName + queueName, 
            _=> _pullingFactory.Invoke(connectionStringOrName,queueName));
    }
}
